using UnityEngine;
using System.Collections.Generic;

/*A sprite with a synth! Currently only allows one note to be played at a time, though this could be changed.
	Mostly, this is to make the channel tracking easier, but it could be made into a list of channels, so each
	would still have independenct volume control
	*/
public class AudioSprite : DepthManagedSprite {
	
	public const int NUM_NOTES_PER_OCTAVE = 12;
	public const int THIRD = 4;
	public const int FIFTH = 7;
	public const int SEVENTH = 11;
	
	//the most a pitch cn be shifted
	public const float MAX_SHIFT = 12f;
	
	//the synth
	public Synth synth;
	
	//the percent volume with which the sprite starts notes
	protected float noteVolume;
	
	//current note volume % from 0 to 1
	protected float channelVolume = 0;
	
	//the current note
	public int note;
	
	//The instrument number from the sound bank
	private int instrument = 94;
	
	//the current audio channel
	protected int channel;
	
	//returns the pitch-bent note that's being played
	public float Note {
		get {return note + totalBend;}
	}
	
	private bool bending = false;
	
	//amount of pitch bend
	float oldBend = 0;//the old, resolved bend value
	float currentBend = 0;//the currently shifting bend
	float totalBend = 0; //the combined value
	
	//Is a note playing?
	protected bool playing = false;
	public bool Playing {
		get{return playing;}
	}
	
	//The set of the notes in harmony with this aSprite's
	//Currently only using the major 7th, but could easily add more notes
	private HashSet<int> harmonyNotes;
	
	// Use this for initialization
	override protected void Start () {
		base.Start();
		harmonyNotes = new HashSet<int>();
	}
	
	// Update is called once per frame
	protected virtual void Update () {
		
		if(Input.GetKeyDown(KeyCode.LeftBracket)) setInstrument(instrument-1);
		else if(Input.GetKeyDown(KeyCode.RightBracket)) setInstrument(instrument+1);
	}
	
	//play a random note within the synth range
	protected void playRandom() {
		playRandom (Synth.MIN_NOTE, Synth.MAX_NOTE);
	}
	
	//Play a random note within this range (inclusive)
	protected void playRandom(int min, int max) {
		//because unity's random range is exclusive of max
		int newNote = UnityEngine.Random.Range (min, max+1);
		play(newNote);
	}
	
	//will stop playing the last note
	protected void play(int newNote) {
		if(playing) stop();//first stop the current note
		note = newNote;
		channel = synth.Play(note, channelVolume, noteVolume, instrument);
		playing  = true;
		updateHarmonySet();
	}
	
	public void stop() {
		if (playing) {
			playing = false;
			synth.Stop(channel, note);
		}
	}
	
	public void setChannelVolume(float newVolume) {
		if(playing) {
			channelVolume = newVolume;
			synth.setChannelVolume (channel, channelVolume);
		}
	}
	
	//Incease the instrument
	private void setInstrument(int instrumentNumber) {
		instrument = instrumentNumber;
		if(playing) play(note);
		Debug.Log (instrument);
	}
	
	/*//increase the note played
	public void increaseNote() {
		if(!playing) return;
			stop();
			note++;
			play(note);
		
	}
	
	//decrease the note played
	public void decreaseNote() {
		if(!playing) return;
		stop();
		note--;
		play (note);
	}*/
		
	public void toggleBend() {
		bending = !bending;
		if(!bending) handleBendMerge();
	}
	
	public void bendNote(float bendAmt ) {
		if(!playing || !bending) return;
		currentBend = bendAmt;
		totalBend = oldBend + currentBend;
		if(Mathf.Abs(totalBend) > MAX_SHIFT) handleBendMerge(); //total shifting has gone over the cap
		synth.setPitchBend(channel, totalBend);
	}
	
	//merge the two bend values together, creating a new note
	private void handleBendMerge() {
		
		//don't need to do anything
		if(Mathf.Abs(totalBend) < 1) return;
		
		int intBend;
		if(totalBend > 0) intBend = Mathf.FloorToInt(totalBend);
		else intBend = Mathf.CeilToInt(totalBend);
		oldBend = totalBend - intBend;//update the old bend to the bend left over
		currentBend = 0;
		totalBend = oldBend;//reset other bend data
		
		stop();//stop playing the current  note
		play(bindNote(note + intBend));//play the new note
		synth.setPitchBend (channel, oldBend);
		updateHarmonySet();//update the harmony set to reflect the new values
	}
	
	//update the set of notes that are harmonious with this character's
	private void updateHarmonySet() {
		harmonyNotes.Clear();
		harmonyNotes.Add (note % NUM_NOTES_PER_OCTAVE);
		harmonyNotes.Add ( (note + THIRD) % NUM_NOTES_PER_OCTAVE);
		harmonyNotes.Add ( (note + FIFTH) % NUM_NOTES_PER_OCTAVE);
		//harmonyNotes.Add ( (note + SEVENTH) % NUM_NOTES_PER_OCTAVE);
	}
	
	//returns true if otherNote is harmonious with note
	protected bool isHarmony(int otherNote) {
		return harmonyNotes.Contains(otherNote % NUM_NOTES_PER_OCTAVE);
	}
	
	/*
	 * if othernote is a full-step or more from a harmony note, return 0
	 * else return the percent away within that step
	 * */
	protected float getHarmonyPercent(float otherNote) {
		
		int lowNote = Mathf.FloorToInt(otherNote);
		int highNote = Mathf.CeilToInt(otherNote);
				
		if(isHarmony(lowNote)) return 1 - (otherNote-lowNote);
		else if(isHarmony(highNote)) return 1 - (highNote-otherNote);
		else return 0f;
	}
	
	//bind a note to min/max range
	private int bindNote(int note) {
		if (note < Synth.MIN_NOTE) return Synth.MIN_NOTE;
		else if(note > Synth.MAX_NOTE) return Synth.MAX_NOTE;
		else return note;
	}
}
